[Kotlin]レシーバー指定ラムダとは何か

[Kotlin]レシーバー指定ラムダとは何か

Clock Icon2017.06.30

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

まえがき

Kotlinのスコープ関数便利ー。

let apply run with also

kotlin-Standard.ktより

ただ本当に使いこなすには ラムダレシーバー指定ラムダ を理解しなければなりません。

// ラムダ
public inline fun  T.let(block: (T) -> R): R = block(this)
// レシーバー指定ラムダ
public inline fun  T.apply(block: T.() -> Unit): T { block(); return this }

block: (T) -> R)block: T.() -> Unit の違いわかりますか

これを理解しないとblock内の this の罠に引っかかる

サンプル

理解するためのサンプルコードを書きました

class Sample {
    val lambda: (String) -> Unit = {
        Log.d("test", this.javaClass.simpleName) //Sample
        Log.d("test", it) //test
    }

    val receiverLambda: String.() -> Unit = {
        Log.d("test", this.javaClass.simpleName) //String
        Log.d("test", this) //test
    }
}

thisの値が違いますね。なぜこういう動きになるのかKotlin BytecodeをDecompileにして、おってみましょう。

Kotlin BytecodeからDecompile

Android Studioをもっていれば簡単にKotlin BytecodeをDecompileできます。

スクリーンショット 2017-06-30 17.01.13 スクリーンショット_2017-06-30_16_53_59

Decompileしたものがこちら。

// ... (Java decompiled code)

ラムダ

// ... (Java decompiled code for lambda)

ラムダは、Function1というインターフェースがつくられ、invokeという関数ができます。itという名のStringを引数なっていますね。

thisはFunction1インターフェースを実装した場所すなわち、Sampleになります。

レシーバー指定ラムダ

// ... (Metadata for receiverLambda)

ExtensionFunctionTypeつまり、拡張関数です。この場合はStringにgetReceiverLambdaというメソッドを作っていますね

このFunction1の定義場所はStringなのでthisはStringになります。

こんなこともできちゃいます。

class Sample {

    //この関数内で使用できる拡張関数。Stringにlog()を拡張
   fun log(log: String.() -> Unit) {
        "test".log()
        "hoge".log()
        "foo".log()
    }

    fun showLog() {
        log { print(this) }
        log { Log.d("hoge", this) }
    }
}

fun log()内だけで有効なString拡張関数をいれるイメージです。これを利用するとKotlinっぽいDSLが作りやすくなりますので、何度サンプルを書いて試してみることをおすすめします。

まとめ

ラムダとレシーバー指定ラムダはなんとなくイメージできましたか?

レシーバーって結局なんだ?っと思った方いるかもしれません。それは次回の話のネタに。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.